package com.xyzwps.example.stream;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

public abstract class Stream<T> {

    public abstract Parcel<T> next();

    public final <R> Stream<R> map(Function<T, R> mapFn) {
        return this.operate(new MapOperator<>(mapFn));
    }

    public final Stream<T> filter(Predicate<T> predicateFn) {
        return this.operate(new FilterOperator<>(predicateFn));
    }

    public final Stream<T> compact() {
        return this.filter(Objects::nonNull);
    }

    public final Stream<T> unique() {
        return this.operate(new UniqueOperator<>());
    }

    public final <R> Stream<R> flatMap(Function<T, Stream<R>> flatMapFn) {
        return this.operate(new StreamFlatMapOperator<>(flatMapFn));
    }

    public final <R> Stream<R> operate(Operator<T, R> operator) {
        return operator.handle(this);
    }


    public final <R> R collect(Collector<T, R> collector) {
        while (true) {
            var parcel = this.next();
            switch (parcel) {
                case Parcel.Done<T> ignored:
                    return collector.result();
                case Parcel.Failure<T> failure:
                    throw new RuntimeException(failure.cause());
                case Parcel.Delivery<T> delivery:
                    collector.onRequest(delivery.value());
                    break;
                default:
                    throw new RuntimeException("TODO: undefined parcel " + parcel.getClass().getCanonicalName());
            }
        }
    }
}
